use crate::context::AppContext;
use crate::db::Database;
use crate::{db, vo};
use common_lib::BalanceType::{Available, FrozenFee, FrozenMargin};
use common_lib::ErrorCode::CodeBalanceNotEnough;
use common_lib::{BalanceType, CommonResponseHelper};
use dotenvy::dotenv;
use proto_lib::asset::asset_service_server::{AssetService, AssetServiceServer};
use proto_lib::{asset, AssetBalance, CommonResponse, QueryRequest, QueryResponse, UpdateRequest};
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::error::Error;
use std::ops::Add;
use std::str::FromStr;
use std::sync::Arc;
use tonic::{Request, Response, Status};

#[derive(Debug)]
pub struct MyAssetService {
    context: AppContext,
}

impl MyAssetService {
    fn new(context:AppContext)->Self {
        Self {context}
    }
    fn validate_update_request(&self,req: &UpdateRequest)->Result<(),Status> {
        Ok(())
    }

    fn validate_query_request(&self,req: &QueryRequest)->Result<(),Status> {
        Ok(())
    }

    async fn add_balance(&self, user_id: u32, asset:u16, t: BalanceType, change:Decimal) -> Result<(), Box<dyn Error>> {
        let db = self.context.db.as_ref();
        // 读记录
        let balance = db.get_user_balance(user_id, asset, t).await.unwrap();
        if balance.is_none() { // 记录不存在
            // 新增记录
            db.create_balance(vo::Balance{
                user_id,
                asset,
                t,
                value: change,
            }).await.expect("add_balance error");
            return Ok(());
        }
        let b = balance.unwrap();
        // 修改记录
        db.update_balance(user_id, asset, t, change, b.version).await
    }

    async fn sub_balance(&self, user_id: u32, asset:u16, t: BalanceType, change:Decimal) -> Result<(), Box<dyn Error>> {
        let db = self.context.db.as_ref();
        // 读记录
        let balance = db.get_user_balance(user_id, asset, t).await?;
        if balance.is_none() { //记录不存在
            return Err(Box::new(CodeBalanceNotEnough));
        }
        let b = balance.unwrap();
        let value = b.value;
        if value.lt(&change) {
            return Err(Box::new(CodeBalanceNotEnough));
        }
        // 修改记录
        db.update_balance(user_id, asset, t, change, b.version).await
    }

    fn update_asset_balance(&self,item: &mut AssetBalance, row: &db::Balance) {
        match BalanceType::from_code(row.t).unwrap() {
            Available => {
                item.balance.as_mut().unwrap().available = row.value.to_string();
                    // Some(Decimal::from_str(&*row.value.to_string()).unwrap());
            }
            FrozenFee | FrozenMargin => {
                // let v: common::Decimal = item.balance.clone().unwrap().frozen.unwrap_or(common::Decimal { value: "0".to_string() });
                // // let v: common::Decimal = item.balance.clone().unwrap().frozen.unwrap_or(common::Decimal::zero());
                // item.balance.as_mut().unwrap().frozen = Some(common::Decimal {
                //     value: row.value.add(Decimal::from_str(&*v.value).unwrap()).to_string(),
                // });
                let v = item.balance.clone().unwrap().frozen; // todo 如何能够不clone?
                item.balance.as_mut().unwrap().frozen = row.value.add(Decimal::from_str(&*v).unwrap()).to_string();

            }
            _ => {}
        }
    }

    fn new_asset_balance(&self,row: &db::Balance) -> AssetBalance {
        let mut r = AssetBalance {
            asset: row.asset as u32,
            balance: Some(asset::Balance {
                available: "0".to_string(),
                frozen: "0".to_string(),
            }),
        };

        match BalanceType::from_code(row.t).unwrap() {
            Available => {
                r.balance.as_mut().unwrap().available = row.value.to_string();
            }
            FrozenFee | FrozenMargin => {
                r.balance.as_mut().unwrap().frozen = row.value.to_string();
            }
            _ => {}
        }
        r
    }
}

#[tonic::async_trait]
impl AssetService for MyAssetService {
    async fn update(&self, request: Request<UpdateRequest>) -> Result<Response<CommonResponse>, Status> {
        let req = request.into_inner();
        self.validate_update_request(&req).expect("invalid update request");
        let change:Decimal = req.change.parse().unwrap();
        if req.inc {
            self.add_balance(req.user_id, req.asset as u16, Available, change).await.unwrap();
        }
        else {
            self.sub_balance(req.user_id, req.asset as u16, Available, change).await.unwrap();
        }
        // Ok(Response::new(CommonResponse {
        //     code: 0,
        //     error_msg: None,
        // }))
        Ok(Response::new(CommonResponse::from(CommonResponseHelper::ok())))
    }

    async fn query(&self, request: Request<QueryRequest>) -> Result<Response<QueryResponse>, Status> {
        let req = request.into_inner();
        self.validate_query_request(&req).expect("invalid query request");
        let db = Database::new().await.unwrap();
        let rows = db.get_user_balance_list(req.user_id,Some(req.assets.as_slice())).await.unwrap();
        let mut t: HashMap<u16, AssetBalance> = HashMap::new();

        // let mut resp = QueryResponse{ balances: vec![] };
        // todo 目前只处理available,frozen数据
        for row in rows {
            match t.get_mut(&row.asset) {
                Some(balance) => {
                    self.update_asset_balance(balance, &row);
                }
                None => {
                    t.insert(row.asset, self.new_asset_balance(&row));
                }
            }
        }
        let result:Vec<AssetBalance> = t.into_values().collect();
        Ok(Response::new(QueryResponse{
            balances: result,
        }))
    }
}


pub async fn start_server() -> Result<(), Box<dyn Error>> {
    dotenv().ok();
    env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
    let database = Arc::new(
        Database::new()
            .await
            .expect("Failed to connect to database")
    );
    let context = AppContext {
        db: database,
    };

    let addr = "[::1]:50051".parse()?;
    let asset_service = MyAssetService::new(context);

    println!("Server listening on {}", addr);
    // let reflection_service = tonic_reflection::server::Builder
    // ::configure()
    //     .register_encoded_file_descriptor_set(proto_lib::FILE_DESCRIPTOR_SET)
    //     .build_v1()?;

    tonic::transport::Server
    ::builder()
        .add_service(AssetServiceServer::new(asset_service))
        // .add_service(reflection_service) //
        .serve(addr).await?;

    Ok(())
}